【Scala】Play! の Guice による Akka Actor の DI
はじめに
PlayFramework のバージョン 2.4 以降(以下 PlayFramework)には DI コンテナとして Guice が採用されています。
また、PlayFramework は Scala で書かれたフレームワークで内部的には Akka を利用しています。そのため、PlayFramework のアプリケーションからは Akka Actor を比較的簡単に使用でき、弊社のプロジェクトでもよく利用されています。
Guice による制御の反転を実践している PlayFramework のプロジェクトで Akka Actor を使用する際には、 Guice による Actor の生成 を行うのが良いでしょう。
というわけで、今回は Guice で Akka Actor の生成と Inject を行う方法についてご紹介します。
目次
Actorの準備
Actorを作成する
今回は次のようなアクターを作成しました。
import akka.actor.{ActorLogging, Actor} import SuffixActor.Tell class SuffixActor extends Actor with ActorLogging { val suffix = "なん?" override def receive: Receive = { case Tell(c) => log.info(c + suffix) } } object SuffixActor { case class Tell(content: String) }
受け取った文字列の最後に「なん?」を付加する簡単なアクターです。
例えば SuffixActor.Tell("大丈夫")
というメッセージを受け取ると 大丈夫なん?
というログを出力します。
このアクターは現在のところハードコードされた「なん?」を文字列の末尾に付加しています。
こういった値がハードコードされているのはあまり良くないため、この値を設定ファイルから読み出すようにしたいと思っています。
設定ファイルに値を追加する
今回は Guice によって Inject される PlayFramework の Configuration から値を取得しようと思っています。 そのため、先に設定ファイルに値を追加しておきましょう。
// ... suffixActor.suffix += "なん?" // 追加
これで設定ファイルから値を読みだす準備ができました。
Actorを修正する
先ほどのアクターを設定ファイルから値を読みだすように修正しましょう。
import javax.inject.Inject import akka.actor.{Actor, ActorLogging} import play.api.Configuration import SuffixActor.Tell class SuffixActor @Inject()( configuration: Configuration ) extends Actor with ActorLogging { lazy val suffix = configuration.getString("suffixActor.suffix").get override def receive: Receive = { case Tell(c) => log.info(c + suffix) } } object SuffixActor { case class Tell(content: String) }
通常のクラスへの DI と同じように、 @Inject
で Configuration のインスタンスを受け取ります。もちろん、そのためには SuffixActor 自身も Guice により生成される必要があります。
ActorのDI
AbstractModule の定義
今回の前提は「PlayFramework と Guice を利用しているアプリケーション」です。通常、このようなアプリケーションでは依存性解決のモジュールを定義します。というわけで今回は、Akka Actor の依存性解決モジュールとして ActorDependencyModule を定義したいと思います。
import com.google.inject.AbstractModule import play.api.libs.concurrent.AkkaGuiceSupport class ActorDependencyModule extends AbstractModule with AkkaGuiceSupport { override def configure(): Unit = { bindActor[SuffixActor]("suffixactor") } }
このように定義すれば @Named("suffixactor")
という名前付き解決で SuffixActor の ActorRef を取得できるようになります。
また、依存性解決モジュールを作成しただけでは反映されないため、設定ファイルにモジュールへの参照を追加します。
// ... suffixActor.suffix += "なん?" play.modules.enabled += "ActorDependencyModule" // 追加
これで @Named("suffixactor")
の名前付き解決で「 Configuration が注入された SuffixActor の ActorRef 」 を取得する準備が整いました。
実際に試してみる
初期化時に SuffixActor に対し「残業」というメッセージを送るだけのモジュールを作成しました。
import javax.inject.{Named, Inject, Singleton} import akka.actor.ActorRef @Singleton class SuffixActorBootstrap @Inject() ( @Named("suffixactor") suffixActor: ActorRef ) { suffixActor ! SuffixActor.Tell("残業") }
このクラスを Guice に EagerSingleton として生成させることで、アプリケーション起動時に一度だけ「残業」メッセージが送信されるようになります。
import com.google.inject.AbstractModule import play.api.libs.concurrent.AkkaGuiceSupport class ActorDependencyModule extends AbstractModule with AkkaGuiceSupport { override def configure(): Unit = { bindActor[SuffixActor]("suffixactor") bind(classOf[SuffixActorBootstrap]).asEagerSingleton() // 追加 } }
実際に activator run
などで実行し localhost:9000
にアクセスして初期化処理を走らせると 残業なん?
というログが出力されます。
また試しに application.conf の設定値を変えて再起動してみると、ちゃんと反映されていることが確認できると思います。
注意すべきポイント
Guice による Akka Actor の依存性解決は非常に便利です。
しかし、一つ注意しなければならないポイントがあります。それは Guice により生成される Actor は system 直下に配置されることです。
独自に定義した Supervisor の子アクターとしてアクターを関連付けたい場合には Guice による生成を行うことができません。
この問題は、子アクターの生成は Supervisor 自身が行い、子アクターが必要とする依存モジュールは Supervisor に Inject し、 Supervisor 自身は Guice により生成させることで解決可能です。
註: play.api.libs.concurrent.InjectedActorSupport をミックスインすると injectChild(create: => Actor, name: String) が利用でき、 Guice のファクトリ自動生成と併せれば Guice による子アクターの生成が可能に思えます。しかしながら、この方法では Supervisor に管理される他の ActorRef を子アクターに Inject できません。これの解決方法としては、Supervisor がイベントバスになる、アクターが context.become を利用する前提で ActorRef 待ちのステートを持つなどが挙げられますが、前者は複雑さの観点、後者はフォールトトレランスの観点から無理筋と考えられます。
まとめ
- Guice は Akka Actor を扱うことができます。
- Supervisor に紐付く子アクターについては Guice に生成させず Supervisor 自身が生成し、子アクターの依存モジュールは Supervisor に DI しましょう。
PlayFramework と Akka Actor をフル活用し、耐障害性の高いアプリケーションを作りましょう! ではまた!